package request

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

var ModuleName = "gitee.com/changeden/easyopen-request-go"

// 相关服务框架：https://gitee.com/durcframework/easyopen

type Client struct {
	Url     string
	AppKey  string
	Secret  string
	Jwt     string
	Timeout *time.Duration
	Logger  *log.Logger

	name      string
	version   string
	timestamp *time.Time
	format    string
	data      *map[string]interface{}
	params    interface{}
}

var DefaultTimeout = time.Duration(60) * time.Second

var getRequestBasePath = "?name=%s&version=%s&app_key=%s&data=%s&timestamp=%s&format=%s&sign=%s"

func (client *Client) log(label string, info interface{}) {
	if client.Logger != nil {
		client.Logger.Printf("[%s][%s]: %s", ModuleName, label, info)
	}
}

func getTime(d *time.Time) *time.Time {
	if d == nil {
		n := time.Now()
		return &n
	}
	return d
}

func dateTimeFormat(d *time.Time) string {
	return getTime(d).Format("2006-01-02 15:04:05")
}

func encode(v interface{}) string {
	return url.QueryEscape(v.(string))
}

func (client *Client) SetName(name string) *Client {
	client.name = name
	return client
}

func (client *Client) SetTimestamp(d *time.Time) *Client {
	client.timestamp = d
	return client
}

func (client *Client) SetVersion(version string) *Client {
	client.version = version
	return client
}
func (client *Client) SetFormat(format string) *Client {
	client.format = format
	return client
}
func (client *Client) SetData(data *map[string]interface{}) *Client {
	client.data = data
	return client
}

func (client *Client) genParams() map[string]interface{} {
	params := client.genReqData(client.name, client.version, client.data, client.timestamp)
	return params
}

func (client *Client) Get(t interface{}) error {
	params := client.genParams()
	path := fmt.Sprintf(getRequestBasePath,
		encode(params["name"]),
		encode(params["version"]),
		encode(params["app_key"]),
		encode(params["data"]),
		encode(params["timestamp"]),
		encode(params["format"]),
		encode(params["sign"]))
	return client.req("GET", path, nil, t)
}

func (client *Client) Post(t interface{}) error {
	params := client.genParams()
	dataJson, _ := json.Marshal(params)
	dataJsonStr := string(dataJson)
	reader := strings.NewReader(dataJsonStr)
	return client.req("POST", "", reader, t)
}

func (client *Client) req(method, path string, reader io.Reader, t interface{}) error {
	mUrl := client.Url + path
	client.log("请求地址", mUrl)
	client.log("请求方法", method)
	if method == "POST" && client.params != nil {
		client.log("请求参数", client.params)
	}
	req, err := http.NewRequest(method, mUrl, reader)
	if err != nil {
		client.log("异常信息", err)
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	if client.Jwt != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", client.Jwt))
	}
	timeout := client.Timeout
	if timeout == nil {
		timeout = &DefaultTimeout
	}
	httpClient := &http.Client{
		Timeout: *timeout,
	}
	res, err := httpClient.Do(req)
	if err != nil {
		client.log("异常信息", err)
		return err
	}
	body, err := ioutil.ReadAll(res.Body)
	client.log("请求结果", body)
	defer res.Body.Close()
	if err != nil {
		client.log("异常信息", err)
		return err
	}
	err = json.Unmarshal(body, &t)
	if err != nil {
		client.log("异常信息", err)
		return err
	}
	return nil
}

func (client *Client) genReqData(name, version string, data *map[string]interface{}, timestamp *time.Time) map[string]interface{} {
	dataJson, _ := json.Marshal(data)
	dataJsonStr := string(dataJson)
	format := client.format
	if format == "" {
		format = "json"
	}
	params := map[string]interface{}{
		"name":      name,
		"version":   version,
		"timestamp": dateTimeFormat(timestamp),
		"data":      url.QueryEscape(dataJsonStr),
		"app_key":   client.AppKey,
		"format":    format,
	}
	signature := client.genSign(params)
	params["sign"] = signature
	client.params = &params
	return params
}

func (client *Client) genSign(data map[string]interface{}) string {
	secret := client.Secret
	result := ""
	var keys []string
	for key := range data {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for _, key := range keys {
		result = fmt.Sprintf("%s%s%s", result, key, data[key])
	}
	return strings.ToUpper(fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%s%s%s", secret, result, secret)))))
}
